/*
PearReview - The peer review assistant.
Copyright (C) 2009  Dimitrij Pankratz, Anton Musichin
http://www.pearreview.com, info@pearreview.com

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

package com.pearreview.application;

import java.io.File;
import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.EventListener;
import java.util.EventObject;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.swing.event.EventListenerList;

public class FileHistory implements Serializable {
	private static final long serialVersionUID = -6910900566177888016L;
	
	private Map<String, List<Entry>> history;
	private transient Comparator<Entry> comparator;
	private transient EventListenerList changeListener;

	public static class Entry implements Serializable {
		private static final long serialVersionUID = -2551073310411555172L;
		private final File file;
		private final Calendar calendar;
		public Entry(File file, Calendar calendar) {
			this.file = file;
			this.calendar = calendar;
		}
		public File getFile() {
			return file;
		}
		public Calendar getCalendar() {
			return calendar;
		}
		/**
		 * Checks if a given String is equal to the file of this entry
		 */
		@Override
		public boolean equals(Object obj) {
			if(obj instanceof Entry) {
				Entry other = (Entry) obj;
				return file.equals(other.file);
			} else if(obj instanceof File) {
				File other = (File) obj;
				return file.equals(other);
			}
			
			return super.equals(obj);
		}
		@Override
		public String toString() {
			return file.toString() + " | " + SimpleDateFormat
					.getDateTimeInstance().format(calendar.getTime());
		}
	}
	
	public static class ChangeEvent extends EventObject {
		private static final long serialVersionUID = 2074919071531716697L;
		private String type;
		public ChangeEvent(Object source, String type) {
			super(source);
			this.type = type;
		}
		/**
		 * Returns the type, where was changed. if it is <code>null</code>
		 * then all types are changed.
		 * @return
		 */
		public String getType() {
			return type;
		}
	}
	
	public interface ChangeListener extends Serializable, EventListener {
		public void typeChanged(ChangeEvent ce);
	}
	
	/**
	 * Returns maximum file count, that can be provided
	 * @return maximum file count
	 */
	public int getMaximumFileCount() {
		return 10;
	}
	
	public void add(File file, String type) {
		add(file, Calendar.getInstance(), type);
	}
	
	public void add(File file, Calendar calendar, String type) {
		add(new Entry(file, calendar), type);
	}
	
	public void add(Entry entry, String type) {
		if(!getHistory().containsKey(type)) {
			getHistory().put(type, createEntryList());
		}
		
		List<Entry> lst = getHistory().get(type);
		if(lst.contains(entry)) {
			lst.remove(entry);
		}
		lst.add(entry);
		
		sortEntryList(lst);
		
		trimmEntryList(lst);
		
		fireChangeListeners(type);
	}
	
	public void remove(Entry entry, String type) {
		remove((Object)entry, type);
	}
	
	public void remove(File file, String type) {
		remove((Object)file, type);
	}
	
	protected void remove(Object value, String type) {
		if(getHistory().containsKey(type)) {
			List<?> lst = getHistory().get(type);
			lst.remove(value);
			
			fireChangeListeners(type);
		}
	}
	
	public void clear(String type) {
		if(getHistory().containsKey(type)) {
			getHistory().remove(type);
			fireChangeListeners(type);
		}
	}
	
	public void clear() {
		getHistory().clear();
		fireChangeListeners((String)null);
	}
	
	public int size(String type) {
		return getHistory().containsKey(type) ? 
				getHistory().get(type).size() : 0;
	}
	
	public int size() {
		return getHistory().size();
	}
	
	public boolean isEmpty() {
		return getHistory().isEmpty();
	}
	
	public boolean isEmpty(String type) {
		return getHistory().containsKey(type) ? 
				getHistory().get(type).isEmpty() : true;
	}
	
	public List<File> getFiles(String type) {
		List<File> lst = createFileList();
		for(Entry entry: getEntries(type)) {
			lst.add(entry.getFile());
		}
		return lst;
	}
	
	public List<Entry> getEntries(String type) {
		List<Entry> lst = createEntryList();
		if(getHistory().containsKey(type)) {
			lst.addAll(getHistory().get(type));
		}
		sortEntryList(lst);
		trimmEntryList(lst);
				
		return lst;
	}
	
	public List<File> getFiles(String[] types) {
		List<File> lst = createFileList();
		for(Entry entry: getEntries(types)) {
			lst.add(entry.getFile());
		}
		return lst;
	}
	
	public List<Entry> getEntries(String[] types) {
		List<Entry> lst = createEntryList();
		for(String type: types) {
			List<Entry> typeList = getEntries(type);
			lst.retainAll(typeList);
			lst.addAll(typeList);
		}
		sortEntryList(lst);
		trimmEntryList(lst);
		
		return lst;
	}
	
	protected EventListenerList getChangeListenerList() {
		if(changeListener == null) {
			changeListener = new EventListenerList();
		}
		
		return changeListener;
	}
	
	protected Map<String, List<Entry>> getHistory() {
		if(history == null) {
			history = new LinkedHashMap<String, List<Entry>>();
		}
		
		return history;
	}
	
	protected Comparator<Entry> getComparator() {
		if(comparator == null) {
			comparator = new Comparator<Entry>() {
				@Override
				public int compare(Entry o1, Entry o2) {
					long t1 = o1.getCalendar().getTimeInMillis();
					long t2 = o2.getCalendar().getTimeInMillis();
					
					long dif = t2 - t1;
					
					int r = dif < 0 ? -1 : (dif > 0 ? 1 : 0);
					
					return r;
				}
			};
		}
		return comparator;
	}
	
	protected void sortEntryList(List<Entry> lst) {
		Collections.sort(lst, getComparator());
	}
	
	protected void trimmEntryList(List<Entry> lst) {
		if(getMaximumFileCount() < 0) {
			return;
		}
		
		int dif = lst.size() - getMaximumFileCount();
		
		while(dif > 0) {
			lst.remove(lst.size() - 1);
			dif--;
		}
	}
	
	protected List<Entry> createEntryList() {
		return new ArrayList<Entry>();
	}
	
	protected List<File> createFileList() {
		return new ArrayList<File>();
	}
	
	public void addChangeListener(ChangeListener listener) {
		getChangeListenerList().add(ChangeListener.class, listener);
	}
	
	public void removeChangeListener(ChangeListener listener) {
		getChangeListenerList().remove(ChangeListener.class, listener);
	}
	
	protected ChangeListener[] getChangeListeners() {
		return getChangeListenerList().getListeners(
				ChangeListener.class);
	}
	
	protected void fireChangeListeners(String type) {
		ChangeEvent ce = new ChangeEvent(this, type);
		fireChangeListeners(ce);
	}
	
	protected void fireChangeListeners(ChangeEvent ce) {
		for(ChangeListener listener: getChangeListeners()) {
			listener.typeChanged(ce);
		}
	}
	
	@Override
	public boolean equals(Object obj) {
		if(obj instanceof FileHistory) {
			FileHistory otherHistory = (FileHistory) obj;
			return getHistory().equals(otherHistory.getHistory());
		} else if(obj instanceof Map<?, ?>) {
			return getHistory().equals(obj);
		}
		
		return super.equals(obj);
	}
	
	@Override
	public String toString() {
		return getHistory().toString();
	}
}
